#-------------------------------------------------------------------------------
# gcstat.py
#
# Get status of projects from Google Code.
#
# To do:
#
#   reports in XML and HTML format
#   column indexes should be externally configurable
#   remove hardcoded names
#   improve error handling
#
# Copyright (c) Neil Butterworth 2011
#-------------------------------------------------------------------------------

from urllib import urlopen
from HTMLParser import HTMLParser
import sys

#-------------------------------------------------------------------------------
# These are the hard-coded offsets of the various interesting fields in the 
# downloads page. They should be replaced if you use anything other than the
# default GC field order.
#-------------------------------------------------------------------------------

FILENAME_COL = "0"
DESC_COL = "1"
DOWNLOAD_COL = "5"


#-------------------------------------------------------------------------------
# helpper to get class value from attribute list
#-------------------------------------------------------------------------------

def GetClass( attrs ):
    for attr in attrs:
        if ( attr[0] == "class" ):
            return attr[1]
    return ""

def GetAttr( aname, attrs ):
    for attr in attrs:
        if ( attr[0] == aname ):
            return attr[1]
    return ""
   
#-------------------------------------------------------------------------------
# Encapsulate the downloads HTML page, allowing you to get stats on how many
# downloads there have been for each downloadable file
#-------------------------------------------------------------------------------

class DownloadsPage( HTMLParser ):

    def __init__( self ):
        HTMLParser.__init__( self )
        self.desc = ""
        self.col = ""
        self.filename = ""
        self.filedesc = ""
        self.download = ""
        self.downloads = []
        
    def handle_starttag( self, tag, attrs ):
        if ( tag == 'td' ):
            self.DoTdTag( attrs )
        
                     
    def handle_data( self, data ):
        text = data.strip()
        if (text == "" ):
            return
        self.text = text
        #print self.col, '[' + self.text + ']'
        if ( self.col == FILENAME_COL ):
            self.filename = self.text
        elif ( self.col == DESC_COL ):
            self.filedesc = self.text
        elif ( self.col == DOWNLOAD_COL ):
            self.download = self.text
            self.downloads.append( [self.filename, self.filedesc, self.download ])
            #print "------", self.col, self.filename, self.filedesc, self.download
 
    
    def DoTdTag( self, attrs ):
        cls = GetClass( attrs )
        if ( cls.startswith( "vt col_") or cls.startswith( "vt id col_")):
            self.col = cls[len(cls)-1]
        else:
            self.col = ""
                             
    def Downloads( self ):
        return self.downloads
 

#-------------------------------------------------------------------------------
# Allows you to get the number of open issues on the issues page. Doesn't 
# currently give you issue details.
#-------------------------------------------------------------------------------
 
class IssuesPage (HTMLParser):
    
    def __init__( self ):
        HTMLParser.__init__( self )
        self.issues = 0
 
    def handle_starttag( self, tag, attrs ):  
        if ( GetClass( attrs ) == "vt id col_0"):
            self.issues = self.issues + 1
            
    def Issues( self ):
        return self.issues

#-------------------------------------------------------------------------------
# Use this to display last commit date/time
#-------------------------------------------------------------------------------

class CodePage (HTMLParser):
    
    def __init__( self ):
        HTMLParser.__init__( self )
        self.commit = ""
        
    def handle_starttag( self, tag, attrs ):  
        if ( tag == "td" and GetAttr( "title", attrs ) != "" and self.commit == ""):
            self.commit = GetAttr( "title", attrs )
    
    def LastCommit( self ):
        return self.commit
        
#-------------------------------------------------------------------------------
# General wrapper for status of a single project identified by its GC url.
#-------------------------------------------------------------------------------
    
class ProjectStatus:

    def __init__( self, url ):
        self.url = url
        self.ReadSite()

    def URL( self ):
        return self.url

    def Title( self ):
        return self.dlpage.Title()
 
    def Desc( self ):
        return self.dlpage.Desc()
    
    def ReadSite( self ):
        #print "Reading", self.url
        text = urlopen( self.url + "downloads/list" ).read()
        self.dlpage = DownloadsPage()
        self.dlpage.feed( text )
        text = urlopen( self.url + "issues/list" ).read()     
        self.issuespage = IssuesPage()
        self.issuespage.feed( text )
        text = urlopen( self.url + "source/list" ).read()     
        self.codepage = CodePage()
        self.codepage.feed( text )
        
    def Downloads( self ):
        return self.dlpage.Downloads()
    
    def Issues( self ):
        return self.issuespage.Issues()

    def LastCommit( self ):
        return self.codepage.LastCommit()
    
#-------------------------------------------------------------------------------
# User profile page is where we pick up all the project names for a user from.
#-------------------------------------------------------------------------------

class ProfilePage( HTMLParser ):

    def __init__( self ):
        HTMLParser.__init__( self )
        self.isid = False
        self.projects = []        
    
    def handle_starttag( self, tag, attrs ):  
        if ( tag == "td"):
            self.isid = GetClass( attrs ) == "id"
   

    def handle_data( self, data ):
        text = data.strip()
        if ( text != "" and self.isid ):
            self.projects.append( text )
            self.isid = False

    def Projects( self ) :
        return self.projects

#-------------------------------------------------------------------------------
# Queries the user profile and uses the list of names obtained there to get
# stats for each project. Call the report functions to format the data.
#-------------------------------------------------------------------------------
  
class Report:
    
    def __init__( self, id, writer ):
        user = "http://code.google.com/u/" + id
        print "Reading projects from " + user + "..."
        text = urlopen( user ).read()
        pp = ProfilePage()
        pp.feed( text )
        
        if ( len(pp.Projects()) == 0 ):
            print "No projects for " + id
            return
        else:
            print "There are",  len(pp.Projects()), "projects - retrieving details..."  
                 
        for pname in pp.Projects():
            purl = "http://code.google.com/p/" + pname + "/"
            writer.Write( pname, ProjectStatus( purl ))

#-------------------------------------------------------------------------------
# Simple text report writer
#-------------------------------------------------------------------------------

class TextWriter:
    
    def Write( self, pname, stats ):
        print "\nProject: " + pname + "  Issues: ", stats.Issues(), "   " + stats.LastCommit()
        print "--------------------------------------------------------"
        if ( len( stats.Downloads()) == 0):
            print "No downloads" 
        for fn, fd, dlc in stats.Downloads():
            print fn + " ---> " + dlc
   
#-------------------------------------------------------------------------------
# write stats in CSV format
#-------------------------------------------------------------------------------
 
class CSVWriter:

    def Write( self, pname, stats ):
        head = pname + "," + str(stats.Issues()) + "," + str(stats.LastCommit())
        if ( len(stats.Downloads() ) == 0 ):
            print head +  ",0,0"         
        else:
            for fn, fd, dlc in stats.Downloads():
                out = head + "," + fn + "," + str(dlc)
                print out
                
#-------------------------------------------------------------------------------
# Display text report for user who#s name is command line parameter.
#-------------------------------------------------------------------------------
                              
def main():
    if ( len(sys.argv) == 1 ):
        print "gcstat is a tool for printing Google Code project statistics"
        print "Copyright (C) 2011 Neil Butterworth"
        print "\nusage: python gcstat.py googleid"
    else:
        if ( len(sys.argv) == 2 ):
            user = sys.argv[1]
            csvmode = False
        elif ( len( sys.argv ) == 3 ):
            if ( sys.argv[1] == '-csv' ):
                csvmode = True
                user = sys.argv[2]
            else:
                print "Invalid mode: ", sys.argv[1] 
                return           
        else:
            print "Invalid usage"
            return            
        try:
            if ( csvmode ):
                w = CSVWriter()
            else:
                w = TextWriter()
            r = Report( user, w )
        except:
           print "Ooops, looks like you may not have an active internet connection."
                
if __name__ == '__main__':
    main()
